/*
 * Created on Dec 12, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.vectrics.recruiter.document;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.DateField;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.vectrics.SystemException;
import org.vectrics.common.util.DateSync;
import org.vectrics.domain.Domain;
import org.vectrics.recruiter.RecruitServiceLocator;
import org.vectrics.recruiter.config.RecruitConfig;
import org.vectrics.recruiter.user.resume.PorterStemAnalyzer;
import org.vectrics.recruiter.user.resume.ResumeNotValidException;
import org.vectrics.recruiter.user.resume.WordDocumentReader;

/**
 * @author MMoore
 */
public class CandidateDocumentIndexer {
	private transient final Log log = LogFactory.getLog(this.getClass());
	private String indexDirectoryName = null;
	private Analyzer analyzer = new PorterStemAnalyzer();
	private String documentPath = null;
	private String mutex = "indexer-mutex";

	public CandidateDocumentIndexer(Domain domain) {
		indexDirectoryName = RecruitConfig.getRecruitInstance()
				.getCandidateDocumentIndexPath(domain);
		documentPath = RecruitConfig.getRecruitInstance()
				.getCandidateDocumentPath(domain);
	}

	/*
	 * Constructor that forces the indexing directory on this class, so that the
	 * directory is not looked up in the system constants table.
	 */
	public CandidateDocumentIndexer(Domain domain, String indexPath) {
		indexDirectoryName = indexPath;
		documentPath = RecruitConfig.getRecruitInstance()
				.getCandidateDocumentPath(domain);
	}

	/*
	 * public CandidateDocumentIndexer(String indexPath, String documentPath) {
	 * indexDirectoryName = indexPath; this.documentPath = documentPath; }
	 */
	public void close() {
	}

	public Directory getIndexDirectory() throws IOException {
		Directory indexDirectory = FSDirectory.getDirectory(new File(
				indexDirectoryName), true);
		return (indexDirectory);
	}

	public synchronized void wipeResumeIndex() {
		synchronized (this.mutex) {
			try {
				boolean createNew = true;
				IndexWriter writer = new IndexWriter(this
						.getIndexDirectoryName(), analyzer, createNew);
				writer.close();
			} catch (IOException ioe) {
				throw new SystemException(
						"IOException deleting files from index - directory name: "
								+ this.indexDirectoryName, ioe);
			}
		}
	}

	public synchronized Document indexDocument(CandidateDocument document) {
		this.removeDocumentFromIndex(document);
		Document luceneDocument = null;
		try {
			luceneDocument = this.writeDocument(document);
			Date now = DateSync.getCurrentTime();
			document.setLastIndex(now);
			document.setIndexError(false);
			RecruitServiceLocator.getRecruitUserService()
					.updateCandidateDocument(document);
		} catch (Exception ex) {
			document.setIndexError(true);
			log.warn("Error indexing document: " + document.getId(), ex);
		}
		return (luceneDocument);
	}

	public int getDocumentCount() {
		int count = 0;
		IndexWriter writer = null;
		try {
			// String indexDir = "test-index-dir";
			boolean indexExists = (IndexReader.indexExists(this
					.getIndexDirectoryName()));
			log.debug("Getting document count");
			log.debug("  - Index already exists: " + indexExists);
			writer = new IndexWriter(this.getIndexDirectoryName(), analyzer,
					!indexExists);
			count = writer.docCount();
			writer.close();
		} catch (Exception ex) {
			throw new SystemException(
					"Exception opening or accessing index writer", ex);
		}
		return (count);
	}

	private String getDocumentIdKey(CandidateDocument candidateDocument) {
		String key = candidateDocument.getId().toString();
		while (key.length() < 20) {
			key = "0" + key;
		}
		return (key);
	}

	public Document findDocument(CandidateDocument candidateDocument) {
		Document foundDocument = null;
		synchronized (this.mutex) {

			try {
				IndexSearcher searcher = new IndexSearcher(this
						.getIndexDirectoryName());
				Term term = new Term("documentId",
						getDocumentIdKey(candidateDocument));
				TermQuery termQuery = new TermQuery(term);
				Hits hits = searcher.search(termQuery);
				if (hits.length() > 1)
					throw new SystemException(
							"Found more than one Lucene document with the document id: "
									+ candidateDocument.getId());
				if (hits.length() == 1) {
					foundDocument = hits.doc(0);
				}
			} catch (Exception ex) {
				log.error("Exception running search", ex);
			}
		}
		return (foundDocument);
	}

	public void removeDocumentFromIndex(CandidateDocument candidateDocument) {
		synchronized (this.mutex) {
			int numberOfDocs = this.getDocumentCount();
			IndexReader reader = null;
			try {
				reader = IndexReader.open(this.getIndexDirectoryName());
				// Term term = new Term("documentId",
				// resume.getId().toString());
				// reader.delete(term);
				for (int i = 0; i < numberOfDocs; i++) {
					if (reader.isDeleted(i) == false) {
						Document document = (Document) reader.document(i);
						Field field = document.getField("documentId");
						Long documentId = new Long(field.stringValue());
						if (documentId.equals(candidateDocument.getId())) {
							reader.delete(i);
						}
					}
				}
			} catch (IOException ioe) {
				throw new SystemException("Exception opening reader", ioe);
			} finally {
				try {
					reader.close();
				} catch (IOException ioe) {
					log.error("Exception closing reader", ioe);
				}
			}
			candidateDocument.setLastIndex(null);
			RecruitServiceLocator.getRecruitUserService()
					.updateCandidateDocument(candidateDocument);
		}
	}

	private Document writeDocument(CandidateDocument document) {
		log.info("Write document: " + document);
		Document luceneDocument = null;
		synchronized (this.mutex) {
			if (document.getId() == null)
				throw new SystemException(
						"Resume does not have an ID, make sure resume is saved before indexing");
			IndexWriter writer = null;
			try {
				// String indexDir = "test-index-dir";
				boolean indexExists = (IndexReader.indexExists(this
						.getIndexDirectoryName()));
				if (indexExists) {
					removeDocumentFromIndex(document);
				}
				if (log.isDebugEnabled()) {
					log.debug("Adding resume to index");
					log.debug("  - Index already exists: " + indexExists);
				}
				writer = new IndexWriter(this.getIndexDirectoryName(),
						analyzer, !indexExists);

				if (log.isDebugEnabled()) {
					log.debug("About to add document to index ");
					log.debug(" - Doc count: " + writer.docCount());
				}

				String documentFileName = this.documentPath + "/"
						+ document.getSavedFileName();
				File documentFile = new File(documentFileName);
				if (documentFile.exists() == false) {
					throw new ResumeNotValidException(
							"Document file was not found: "
									+ "\r\n - fileName =    "
									+ documentFileName + ""
									+ "\r\n - document.id = "
									+ document.getId());
				}
				try {
					luceneDocument = this
							.createDocumentFromFile(documentFileName);
				} catch (FileNotFoundException fnfe) {
					throw new ResumeNotValidException("Resume file not found: "
							+ documentFileName, fnfe);
				}

				if (document.getId() == null)
					throw new SystemException(
							"Document object not saved (has null id) before indexing, please save the document first.");
				luceneDocument.add(Field.Keyword("documentId", this
						.getDocumentIdKey(document)));

				if (document.getCategory() == null)
					throw new SystemException(
							"Document object missing category before indexing.");
				luceneDocument.add(Field.Keyword("category", document
						.getCategory().getCode()));

				/*
				 * SimpleDateFormat sortDateFormat =
				 * CoreServiceLocator.getDateFormat
				 * ().getDateTimeFormatForSort();
				 * luceneDocument.add(Field.UnIndexed("date",
				 * sortDateFormat.format(document.getDateCreated()))); if
				 * (document.getPerson() != null) {
				 * luceneDocument.add(Field.UnIndexed("personId",
				 * document.getPerson().getId().toString())); } if
				 * (document.getDescription() != null) {
				 * luceneDocument.add(Field.UnIndexed("description",
				 * document.getDescription())); } if (document.getName() !=
				 * null) { luceneDocument.add(Field.UnIndexed("name",
				 * document.getName())); }
				 */
				writer.addDocument(luceneDocument);

				if (log.isDebugEnabled()) {
					log.debug("Finished adding document to index ");
					log.debug(" - Doc count: " + writer.docCount());
					log
							.debug(" - Person:    "
									+ document.getPerson().getName());
					log.debug(" - Path:      " + this.getIndexDirectoryName());
				}
				writer.optimize();
			} catch (Exception ex) {
				throw new SystemException("Exception writing document", ex);
			} finally {
				if (writer != null) {
					try {
						writer.close();
					} catch (Exception e) {
						log.warn("Exception closing writer", e);
					}
				}
			}
		}
		return (luceneDocument);
	}

	public Document createDocument(String resumeText)
			throws java.io.FileNotFoundException {
		Document doc = null;
		if (resumeText != null) {
			// make a new, empty document
			doc = new Document();

			Date now = DateSync.getCurrentTime();
			doc.add(Field.Text("path", "" + now.getTime()));

			// Add the last modified date of the file a field named "modified".
			// Use a
			// Keyword field, so that it's searchable, but so that no attempt is
			// made
			// to tokenize the field into words.
			doc.add(Field.Keyword("modified", DateField.timeToString(now
					.getTime())));

			// log.debug("Creating document, content: " +
			// resumeText.toLowerCase());
			Field field = Field.Text("contents", resumeText);
			doc.add(field);

			// return the document
		} else {
			log.error("Resume does not have text - not indexing.");
		}
		return doc;
	}

	public Document createDocumentFromFile(String fileName)
			throws java.io.FileNotFoundException {
		if (fileName == null)
			throw new SystemException(
					"createDocumentFromFile() was passed a null fileName");
		// make a new, empty document
		Document doc = new Document();

		// Add the path of the file as a field named "path". Use a Text field,
		// so
		// that the index stores the path, and so that the path is searchable
		doc.add(Field.UnIndexed("path", fileName));

		// Add the contents of the file a field named "contents". Use a Text
		// field, specifying a Reader, so that the text of the file is
		// tokenized.
		String fileContents = null;
		if (fileName.endsWith(".doc")) {
			WordDocumentReader docReader = new WordDocumentReader();
			fileContents = docReader.wordDocToString(fileName);
		} else {
			File resumeFile = new File(fileName);
			FileInputStream is = new FileInputStream(resumeFile);
			StringBuffer contents = new StringBuffer();
			boolean done = false;
			try {
				while (!done) {
					int c = is.read();
					if (c < 0)
						done = true;
					else
						contents.append((char) c);
				}
			} catch (Exception ex) {
				throw new SystemException(
						"Exception reading file into string buffer", ex);
			}
			fileContents = contents.toString();
		}

		// log.debug("Adding document with contents: " +
		// contents.toString().toLowerCase());
		if (fileContents.length() > 0) {
			Field field = Field.Text("contents", fileContents);
			doc.add(field);
		} else {
			Field field = Field.Text("contents", " ");
			doc.add(field);
		}
		return doc;
	}

	/**
	 * @return Returns the analyzer.
	 */
	public Analyzer getAnalyzer() {
		return analyzer;
	}

	/**
	 * @param analyzer
	 *            The analyzer to set.
	 */
	public void setAnalyzer(Analyzer analyzer) {
		this.analyzer = analyzer;
	}

	/**
	 * @return Returns the indexDirectoryName.
	 */
	public String getIndexDirectoryName() {
		return indexDirectoryName;
	}

	/**
	 * @param indexDirectoryName
	 *            The indexDirectoryName to set.
	 */
	public void setIndexDirectoryName(String indexDirectoryName) {
		this.indexDirectoryName = indexDirectoryName;
	}
}
